﻿//#define DEBUG_DUMP_SESSION

using Microsoft.Media.CaptureClient.Sample.Common;
using Microsoft.Media.IngestClient;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.Devices.Enumeration;
using Windows.Devices.Input;
using Windows.Graphics.Display;
using Windows.Media;
using Windows.Media.Capture;
using Windows.Media.Devices;
using Windows.Media.MediaProperties;
using Windows.System;
using Windows.System.Display;
using Windows.UI;
using Windows.UI.ApplicationSettings;
using Windows.UI.Core;
using Windows.UI.Popups;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.Web.Http;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace Microsoft.Media.CaptureClient.Sample
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        const string Key_RecordingTerminated = "RecordingTerminated";
        const uint maxVerticalResolution = 480;
        readonly DispatcherTimer recordingTimer = new DispatcherTimer();
        readonly DispatcherTimer interactionTimer = new DispatcherTimer();
        readonly CaptureSession captureSession;
        readonly DisplayRequest displayRequest;

        MediaCapture mediaCapture;
        bool recordingTerminated;
        bool isInteracting = true;
        bool isRetrying = false;
        NavigationHelper navigationHelper;
        RecordingState recordingState = RecordingState.NotRecording;
        DeviceInformation selectedVideoDevice;
        DeviceInformation selectedAudioDevice;
        AudioEncodingProperties audioProps = null;
        VideoEncodingProperties videoProps = null;
        VideoEncodingProperties videoPreviewProps = null;
        Task messageDialogTask;
        int audioFragmentsSent = 0;
        int videoFragmentsSent = 0;
        int audioFragmentsQueued = 0;
        int videoFragmentsQueued = 0;
        int videoFragmentsCreated = 0;
        int audioFragmentsCreated = 0;

        Brush bufferingDefaultBrush;
        Brush bufferingWarningBrush;
        bool bufferingWarning;
                
        /// <summary>
        /// NavigationHelper is used on each page to aid in navigation and 
        /// process lifetime management
        /// </summary>
        public NavigationHelper NavigationHelper
        {
            get { return this.navigationHelper; }
        }

        public MainPage()
        {
            this.InitializeComponent();
            this.navigationHelper = new NavigationHelper(this);
            this.navigationHelper.LoadState += navigationHelper_LoadState;
            this.navigationHelper.SaveState += navigationHelper_SaveState;

            this.DataContext = Preferences.Current;
            interactionTimer.Interval = TimeSpan.FromSeconds(2);
            interactionTimer.Tick += interactionTimer_Tick;

            bufferingDefaultBrush = progressBufferSize.Foreground;
            bufferingWarningBrush = new SolidColorBrush(Colors.Red);
            recordingTimer.Interval = TimeSpan.FromSeconds(1);
            recordingTimer.Tick += recordingTimer_Tick;

            SettingsPane.GetForCurrentView().CommandsRequested += MainPage_CommandsRequested;
            Preferences.Current.VideoEncodingPreferencesChanged += Preferences_VideoEncodingPreferencesChanged;
            Preferences.Current.VideoDeviceIdChanged += Preferences_VideoDeviceIdChanged;
            Preferences.Current.AudioDeviceIdChanged += Preferences_AudioDeviceIdChanged;

            captureSession = new CaptureSession();
            captureSession.FragmentCreated += captureSession_FragmentCreated;
            captureSession.FragmentQueued += captureSession_FragmentQueued;
            captureSession.FragmentSent += captureSession_FragmentSent;
            captureSession.PushFailed += captureSession_PushFailed;
            captureSession.PushRetry += captureSession_PushRetry;
            captureSession.CaptureError += captureSession_CaptureError;
            displayRequest = new DisplayRequest();
        }

        public RecordingState RecordingState
        {
            get { return recordingState; }
            private set
            {
                if (recordingState != value)
                {
#if LOG
                    App.Logger.Log(value.ToString());
#endif
                    var previousRecordingState = recordingState;
                    recordingState = value;
                    VisualStateManager.GoToState(this, value.ToString(), true);

                    if (recordingState == RecordingState.Recording)
                    {
                        displayRequest.RequestActive();
                        Preferences.Current.AreChangesAllowed = false;
                        ButtonSettings.IsEnabled = false;
                        ButtonCamera.IsEnabled = false;
                        DisplayInformation.AutoRotationPreferences = DisplayInformation.GetForCurrentView().CurrentOrientation;
                    }
                    else if (previousRecordingState == RecordingState.Recording)
                    {
                        DisplayInformation.AutoRotationPreferences = DisplayOrientations.None;
                        displayRequest.RequestRelease();
                        Preferences.Current.AreChangesAllowed = true;
                        ButtonSettings.IsEnabled = true;
                        ButtonCamera.IsEnabled = true;
                    }

                    if (recordingState <= RecordingState.PreviewOnly)
                    {
                        if (recordingTimer.IsEnabled) recordingTimer.Stop();
                        IsInteracting = true;
                    }
                    else if (recordingState == RecordingState.StartRecording)
                    {
                        if (!interactionTimer.IsEnabled) interactionTimer.Start();
                        if (!recordingTimer.IsEnabled) recordingTimer.Start();
                    }
                }
            }
        }

        public bool IsInteracting
        {
            get { return isInteracting; }
            private set
            {
                if (interactionTimer.IsEnabled) interactionTimer.Stop();
                if (value && RecordingState > RecordingState.PreviewOnly) // reset the autohide timer
                {
                    interactionTimer.Start();
                }

                if (isInteracting != value)
                {
                    isInteracting = value;
                    if (isInteracting)
                    {
                        VisualStateManager.GoToState(this, "Interacting", true);
                    }
                    else
                    {
                        VisualStateManager.GoToState(this, "NotInteracting", true);
                    }
                }
            }
        }

        public bool IsRetrying
        {
            get { return isRetrying; }
            private set
            {
                if (isRetrying != value)
                {
                    isRetrying = value;
                    if (isRetrying)
                    {
                        textStatus.Text = "Connection failed, retrying...";
                        textStatus.Visibility = Visibility.Visible;
                    }
                    else
                    {
                        textStatus.Text = string.Empty;
                        textStatus.Visibility = Visibility.Collapsed;
                    }
                }
            }
        }

        void recordingTimer_Tick(object sender, object e)
        {
            RecordingTime.Text = captureSession.RecordingTime.ToString(@"h\:mm\:ss");
            ulong bufferSizeInKB = captureSession.BufferSize / 1024;
            double bufferSizeInMB = Math.Round((double)bufferSizeInKB / 1024, 1);
            if (bufferSizeInMB < 1)
            {
                textBufferSize.Text = bufferSizeInKB.ToString(System.Globalization.CultureInfo.CurrentUICulture) + "KB";
            }
            else
            {
                textBufferSize.Text = bufferSizeInMB.ToString(System.Globalization.CultureInfo.CurrentUICulture) + "MB";
            }
            progressBufferSize.Value = bufferSizeInMB;
            BufferingWarning = (bufferSizeInMB > progressBufferSize.Maximum);
        }

        public bool BufferingWarning
        {
            get { return bufferingWarning; }
            set
            {
                if (bufferingWarning != value)
                {
                    bufferingWarning = value;
                    if (BufferingWarning)
                        progressBufferSize.Foreground = bufferingWarningBrush;
                    else
                        progressBufferSize.Foreground = bufferingDefaultBrush;
                }
            }
        }

        void interactionTimer_Tick(object sender, object e)
        {
            if (ButtonCaptureStop.FocusState != FocusState.Keyboard)
            {
                IsInteracting = false;
            }
        }

        void Preferences_VideoEncodingPreferencesChanged(object sender, EventArgs e)
        {
            if (RecordingState == RecordingState.PreviewOnly)
            {
                SetDefaultProfiles(false);
            }
        }

        async void Preferences_AudioDeviceIdChanged(object sender, EventArgs e)
        {
            if (RecordingState == RecordingState.PreviewOnly)
            {
                var audioDevices = await DeviceInformation.FindAllAsync(Windows.Devices.Enumeration.DeviceClass.AudioCapture);
                var audioDevice = (DeviceInformation)audioDevices.FirstOrDefault(d => d.Id == Preferences.Current.VideoDeviceId);
                if (audioDevice != null)
                {
                    selectedAudioDevice = audioDevice;

                    await TeardownCamera();

                    await InitializeCamera();
                }
            }
        }

        async void Preferences_VideoDeviceIdChanged(object sender, EventArgs e)
        {
            if (RecordingState == RecordingState.PreviewOnly)
            {
                var videoDevices = await DeviceInformation.FindAllAsync(Windows.Devices.Enumeration.DeviceClass.VideoCapture);
                var videoDevice = (DeviceInformation)videoDevices.FirstOrDefault(d => d.Id == Preferences.Current.VideoDeviceId);
                if (videoDevice != null)
                {
                    selectedVideoDevice = videoDevice;

                    ButtonCamera.Checked -= ButtonCamera_Checked;
                    ButtonCamera.Unchecked -= ButtonCamera_Unchecked;
                    ButtonCamera.IsChecked = GetCameraLocation(videoDevice) == Windows.Devices.Enumeration.Panel.Front;
                    ButtonCamera.Checked += ButtonCamera_Checked;
                    ButtonCamera.Unchecked += ButtonCamera_Unchecked;

                    await TeardownCamera();

                    await InitializeCamera();
                }
            }
        }

        async Task SetDefaultDeviceProperties()
        {
            // get properties from the devices being used to record audio and video and use them to drive the default encoding profile used.
            // try to get properties from the devices being used to record audio and video and use them to drive the default encoding profile used.
            try
            {
                audioProps = mediaCapture.AudioDeviceController.GetMediaStreamProperties(MediaStreamType.Audio) as AudioEncodingProperties;
            }
            catch { /* occurs when no device is found */ }

            try
            {
                var currentVideoProps = mediaCapture.VideoDeviceController.GetMediaStreamProperties(MediaStreamType.VideoRecord) as VideoEncodingProperties;

                videoProps = mediaCapture.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.VideoRecord)
                    .OfType<VideoEncodingProperties>()
                    .Where(v => v.Subtype == currentVideoProps.Subtype)
                    .OrderByDescending(v => v.Height)
                    .SkipWhile(v => v.Height > maxVerticalResolution)
                    .DefaultIfEmpty(currentVideoProps)
                    .First();

                if (currentVideoProps != videoProps)
                {
                    await mediaCapture.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.VideoRecord, videoProps);
                }
            }
            catch { /* occurs when no device is found */ }

            try
            {
                var currentVideoPreviewProps = mediaCapture.VideoDeviceController.GetMediaStreamProperties(MediaStreamType.VideoPreview) as VideoEncodingProperties;

                videoPreviewProps = mediaCapture.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.VideoPreview)
                    .OfType<VideoEncodingProperties>()
                    .Where(v => v.Subtype == currentVideoPreviewProps.Subtype)
                    .OrderByDescending(v => v.Height)
                    .SkipWhile(v => v.Height > maxVerticalResolution)
                    .DefaultIfEmpty(currentVideoPreviewProps)
                    .First();

                if (currentVideoPreviewProps != videoPreviewProps)
                {
                    await mediaCapture.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.VideoPreview, videoPreviewProps);
                }
            }
            catch { /* occurs when no device is found */ }
        }

        void SetDefaultProfiles(bool updateInputProfile)
        {
            if (updateInputProfile || captureSession.CaptureEncodingProfile == null)
            {
                captureSession.CaptureEncodingProfile = EncodingProfileHelper.GetDefaultInputProfile(audioProps, videoProps);

                UpdateVideoOrientation(videoPreviewProps);
                UpdateVideoOrientation(captureSession.CaptureEncodingProfile.Video);
            }

            var outputProfiles = EncodingProfileHelper.GetDefaultOutputProfiles(Preferences.Current.VideoEncodingPreferences, captureSession.CaptureEncodingProfile);
            captureSession.VideoOutputEncodingProperties = outputProfiles.OfType<VideoEncodingProperties>().ToList();
            captureSession.AudioOutputEncodingProperties = outputProfiles.OfType<AudioEncodingProperties>().ToList();

            foreach (var outputProperties in captureSession.VideoOutputEncodingProperties)
            {
                UpdateVideoOrientation(outputProperties);
            }
        }

        void UpdateProfileOrientation()
        {
            UpdateVideoOrientation(videoPreviewProps);
            UpdateVideoOrientation(captureSession.CaptureEncodingProfile.Video);

            foreach (var outputProperties in captureSession.VideoOutputEncodingProperties)
            {
                UpdateVideoOrientation(outputProperties);
            }
        }

        void MainPage_CommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)
        {
            args.Request.ApplicationCommands.Clear();
            var about = new SettingsCommand("about", "About", c =>
            {
                var flyout = new AboutFlyout();
                flyout.Show();
            });
            args.Request.ApplicationCommands.Add(about);
            var preferences = new SettingsCommand("settings", "Settings", c =>
            {
                var flyout = new PreferencesFlyout();
                flyout.Show();
            });
            args.Request.ApplicationCommands.Add(preferences);
        }

        #region Instrumentation
        async void captureSession_PushRetry(object sender, PushRetryEventArgs e)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
#if LOG
                App.Logger.Log("Push Retry: " + e.Error);
#endif
                IsRetrying = true;
            });
        }

        async void captureSession_PushFailed(object sender, PushFailureEventArgs e)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
            {
#if LOG
                App.Logger.Log("Push Failed: " + e.Error);
#endif
                IsRetrying = false;
                RecordingState = RecordingState.PreviewOnly;
                await ShowMessage(new MessageDialog(e.Error, "Push Failed"));
            });
        }

        async void captureSession_CaptureError(object sender, CaptureErrorEventArgs e)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
#if LOG
                App.Logger.Log("CaptureSession Error: " + e.Error);
#endif
            });
        }

        async void mediaCapture_Failed(MediaCapture sender, MediaCaptureFailedEventArgs errorEventArgs)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
            {
#if LOG
                App.Logger.Log("MediaCapture Failed: " + errorEventArgs.Message);
#endif
                if (RecordingState != RecordingState.NotRecording)
                {
                    RecordingState = RecordingState.NotRecording;
                    await TeardownCamera();
                    await ShowMessage(new MessageDialog(errorEventArgs.Message, "MediaCapture Failed"));
                    await InitializeCamera();
                }
            });
        }

        /// <summary>
        /// Shows message dialogs one at a time.
        /// </summary>
        /// <param name="messageDialog">The MessageDialog to show.</param>
        /// <returns>An awaitable task</returns>
        async Task ShowMessage(MessageDialog messageDialog)
        {
            if (messageDialogTask != null)
            {
                do
                {
                    await messageDialogTask;
                    await Task.Yield(); // let the original caller finish first and set messageTask to null.
                } while (messageDialogTask != null);
            }
            if (Window.Current.Visible)
            {
                messageDialogTask = messageDialog.ShowAsync().AsTask();

                await messageDialogTask;
                messageDialogTask = null;
            }
        }

        async void captureSession_FragmentQueued(object sender, FragmentDeliveryEventArgs e)
        {
            var isVideo = e.Fragment.MovieFragment.TrackFragment.TrackFragmentHeader.TrackId <= captureSession.VideoOutputEncodingProperties.Count;
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                if (isVideo)
                {
                    videoFragmentsQueued++;
                    textVideoQueued.Text = videoFragmentsQueued.ToString();
                }
                else
                {
                    audioFragmentsQueued++;
                    textAudioQueued.Text = audioFragmentsQueued.ToString();
                }
            });
        }

        async void captureSession_FragmentSent(object sender, FragmentDeliveryEventArgs e)
        {
            var isVideo = e.Fragment.MovieFragment.TrackFragment.TrackFragmentHeader.TrackId <= captureSession.VideoOutputEncodingProperties.Count;
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                if (e.Purpose == FragmentDeliveryPurpose.Normal)
                {
                    if (isVideo)
                    {
                        videoFragmentsSent++;
                        textVideoSent.Text = videoFragmentsSent.ToString();
                    }
                    else
                    {
                        audioFragmentsSent++;
                        textAudioSent.Text = audioFragmentsSent.ToString();
                    }
                    IsRetrying = false;
                }
            });
        }

        async void captureSession_FragmentCreated(object sender, FragmentCreatedEventArgs e)
        {
            var isVideo = e.StreamIndex < captureSession.VideoOutputEncodingProperties.Count;
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                if (isVideo)
                {
                    videoFragmentsCreated++;
                    textVideoCreated.Text = videoFragmentsCreated.ToString();
                }
                else
                {
                    audioFragmentsCreated++;
                    textAudioCreated.Text = audioFragmentsCreated.ToString();
                }
            });
        }

        private void ResetCounters()
        {
            audioFragmentsSent = 0;
            videoFragmentsSent = 0;
            audioFragmentsQueued = 0;
            videoFragmentsQueued = 0;
            audioFragmentsCreated = 0;
            videoFragmentsCreated = 0;

            textAudioCreated.Text = "0";
            textVideoCreated.Text = "0";
            textAudioQueued.Text = "0";
            textVideoQueued.Text = "0";
            textAudioSent.Text = "0";
            textVideoSent.Text = "0";
            textBufferSize.Text = "";
            progressBufferSize.Value = 0;
        }
        #endregion

        async Task InitAsync()
        {
            var videoDevices = await DeviceInformation.FindAllAsync(Windows.Devices.Enumeration.DeviceClass.VideoCapture);
            selectedVideoDevice = videoDevices.FirstOrDefault(d => d.Id == Preferences.Current.VideoDeviceId) ?? videoDevices.FirstOrDefault();
            if (videoDevices.Any(d => GetCameraLocation(d) == Windows.Devices.Enumeration.Panel.Front) && videoDevices.Any(d => GetCameraLocation(d) == Windows.Devices.Enumeration.Panel.Back))
            {
                ButtonCamera.Visibility = Visibility.Visible;
                ButtonCamera.Checked -= ButtonCamera_Checked;
                ButtonCamera.Unchecked -= ButtonCamera_Unchecked;
                ButtonCamera.IsChecked = GetCameraLocation(selectedVideoDevice) == Windows.Devices.Enumeration.Panel.Front;
                ButtonCamera.Checked += ButtonCamera_Checked;
                ButtonCamera.Unchecked += ButtonCamera_Unchecked;
            }
            else
            {
                ButtonCamera.Visibility = Visibility.Collapsed;
            }

            var audioDevices = await DeviceInformation.FindAllAsync(Windows.Devices.Enumeration.DeviceClass.AudioCapture);
            var audioDeviceId = MediaDevice.GetDefaultAudioCaptureId(AudioDeviceRole.Communications);
            selectedAudioDevice = audioDevices.FirstOrDefault(d => d.Id == Preferences.Current.AudioDeviceId) ?? audioDevices.FirstOrDefault(d => d.Id == audioDeviceId);
          
            Windows.Graphics.Display.DisplayInformation.GetForCurrentView().OrientationChanged += MainPage_OrientationChanged;

            await InitializeCamera();
        }

        async void MainPage_OrientationChanged(Windows.Graphics.Display.DisplayInformation sender, object args)
        {
            if (mediaCapture != null)
            {
                if (RecordingState == RecordingState.PreviewOnly) // orientation changes should not affect anything while recording
                {
                    UpdateProfileOrientation();

                    if (videoPreviewProps != null)
                    {
                        var rotation = GetVideoRotation();
                        mediaCapture.SetPreviewRotation(rotation);
                        mediaCapture.SetRecordRotation(rotation);
                        await mediaCapture.SetEncodingPropertiesAsync(MediaStreamType.VideoPreview, videoPreviewProps, new MediaPropertySet());
                    }
                }
            }
        }

        VideoRotation GetVideoRotation()
        {
            bool isFront = GetCameraLocation(selectedVideoDevice) == Windows.Devices.Enumeration.Panel.Front;
                    
            switch (Windows.Graphics.Display.DisplayInformation.GetForCurrentView().CurrentOrientation)
            {
                case Windows.Graphics.Display.DisplayOrientations.LandscapeFlipped:
                    return VideoRotation.Clockwise180Degrees;
                case Windows.Graphics.Display.DisplayOrientations.Portrait:
                    return isFront ? VideoRotation.Clockwise270Degrees : VideoRotation.Clockwise90Degrees;
                case Windows.Graphics.Display.DisplayOrientations.PortraitFlipped:
                    return isFront ? VideoRotation.Clockwise90Degrees : VideoRotation.Clockwise270Degrees;
                case Windows.Graphics.Display.DisplayOrientations.Landscape:
                case Windows.Graphics.Display.DisplayOrientations.None:
                default:
                    return VideoRotation.None;
            }
        }

        static void UpdateVideoOrientation(VideoEncodingProperties properties)
        {
            if (IsVideoPortrait(properties) != IsDevicePortrait(properties))
            {
                FlipAspectRatio(properties);
            }
        }

        static void FlipAspectRatio(VideoEncodingProperties properties)
        {
            var width = properties.Width;
            properties.Width = properties.Height;
            properties.Height = width;
        }

        static bool IsVideoPortrait(VideoEncodingProperties properties)
        {
            return properties.Width < properties.Height;
        }

        static bool IsDevicePortrait(VideoEncodingProperties properties)
        {
            var orientation = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().CurrentOrientation;
            return orientation == Windows.Graphics.Display.DisplayOrientations.Portrait || orientation == Windows.Graphics.Display.DisplayOrientations.PortraitFlipped;
        }

        private async Task InitializeCamera()
        {
            if (mediaCapture == null)
            {
                if (selectedVideoDevice != null || selectedAudioDevice != null)
                {
                    mediaCapture = new MediaCapture();
                    mediaCapture.Failed += mediaCapture_Failed;

                    var captureInitSettings = new MediaCaptureInitializationSettings();
                    captureInitSettings.StreamingCaptureMode = StreamingCaptureMode.AudioAndVideo;
                    captureInitSettings.MediaCategory = MediaCategory.Other;
                    if (selectedVideoDevice != null)
                    {
                        captureInitSettings.VideoDeviceId = selectedVideoDevice.Id;
                    }
                    if (selectedAudioDevice != null)
                    {
                        captureInitSettings.AudioDeviceId = selectedAudioDevice.Id;
                    }
                    await mediaCapture.InitializeAsync(captureInitSettings);

                    await SetDefaultDeviceProperties();
                    SetDefaultProfiles(true);

                    if (videoPreviewProps != null) // make sure we found a video input source and set the encoding profile. Without this, there is nothing to preview.
                    {
                        bool isFront = GetCameraLocation(selectedVideoDevice) == Windows.Devices.Enumeration.Panel.Front;
                        captureElement.FlowDirection = isFront ? FlowDirection.RightToLeft : FlowDirection.LeftToRight;

                        var rotation = GetVideoRotation();
                        mediaCapture.SetPreviewRotation(rotation);
                        mediaCapture.SetRecordRotation(rotation);
                        captureElement.Source = mediaCapture;
                        await mediaCapture.StartPreviewAsync();
                        await mediaCapture.SetEncodingPropertiesAsync(MediaStreamType.VideoPreview, videoPreviewProps, new MediaPropertySet());
                    }
                    RecordingState = RecordingState.PreviewOnly;
                }
                else
                {
                    var msgDialog = new MessageDialog("No audio or video sources were found. Please make sure you have a camera attached to this computer and try again.");
                    msgDialog.Commands.Add(new UICommand("Retry"));
                    await ShowMessage(msgDialog);
                    await InitAsync();
                }
            }
        }

        static Windows.Devices.Enumeration.Panel GetCameraLocation(DeviceInformation deviceInformation)
        {
            var enclosureLocation = deviceInformation.EnclosureLocation;
            return enclosureLocation != null ? enclosureLocation.Panel : Windows.Devices.Enumeration.Panel.Unknown;
        }

        private async Task TeardownCamera()
        {
            if (mediaCapture != null)
            {
                if (RecordingState == RecordingState.Recording)
                {
                    await StopRecordAsync();
                }
                if (RecordingState >= RecordingState.PreviewOnly)
                {
                    if (captureElement.Source != null) // make sure a preview is running.
                    {
                        await mediaCapture.StopPreviewAsync();
                    }
                    RecordingState = RecordingState.NotRecording;
                }
                mediaCapture.Failed -= mediaCapture_Failed;
                mediaCapture.Dispose();
                mediaCapture = null;
            }
        }
        
        private async void ButtonCaptureStart_Click(object sender, RoutedEventArgs e)
        {
            await StartRecordAsync();
        }

        private async Task StartRecordAsync()
        {
            RecordingState = RecordingState.StartRecording;

            ResetCounters();

            try
            {
#if DEBUG_DUMP_SESSION
                var savePicker = new Windows.Storage.Pickers.FileSavePicker();
                savePicker.FileTypeChoices.Add("PIFF media", new List<string>() { ".ismv" });
                var saveFile = await savePicker.PickSaveFileAsync();
                if (saveFile != null)
                {
                    var outputStream = await saveFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);
                    await captureSession.StartAsync(mediaCapture, outputStream);
                }
#else
                await captureSession.StartAsync(mediaCapture, new Uri(Preferences.Current.IngestUrl), Preferences.Current.StreamId);
#endif
                RecordingState = RecordingState.Recording;
            }
            catch (OperationCanceledException)
            {
                RecordingState = RecordingState.PreviewOnly;
            }
            catch (Exception ex)
            {
                if (captureSession.HttpResponseStatusCode == HttpStatusCode.Unauthorized)
                {
                    var noawait = ShowMessage(new MessageDialog("Please make sure your IP address is in the allowed list.", "Error Starting Caputre Session: Unauthorized"));
                }
                else
                {
                    var noawait = ShowMessage(new MessageDialog(ex.Message, "Error Starting Caputre Session"));
                }
                RecordingState = RecordingState.PreviewOnly;
            }
        }

        private async void ButtonCaptureStop_Click(object sender, RoutedEventArgs e)
        {
            await StopRecordAsync();
        }

        private async Task StopRecordAsync()
        {
            RecordingState = RecordingState.EndRecording;

            await captureSession.StopAsync();

            if (RecordingState == RecordingState.EndRecording) // make sure this hasn't changed since async call
            {
                RecordingState = RecordingState.PreviewOnly;
            }
        }

        private async void ButtonPreview_Click(object sender, RoutedEventArgs e)
        {
            var view = CoreApplication.CreateNewView();
            int viewId = 0;
            await view.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                var frame = new Frame();
                Window.Current.Content = frame;
                frame.Navigate(typeof(PlayerPage));
                viewId = ApplicationView.GetForCurrentView().Id;
            });

            await ApplicationViewSwitcher.TryShowAsStandaloneAsync(viewId);
        }

        void Window_VisibilityChanged(object sender, VisibilityChangedEventArgs e)
        {
            // we have a 30 second grace period of staying active.
        }

        private void ButtonHost_Tapped(object sender, TappedRoutedEventArgs e)
        {
            e.Handled = true; // prevent event from bubbling up to ButtonContainer
        }

        void InteractionPanel_Tapped(object sender, TappedRoutedEventArgs e)
        {
            if (RecordingState > RecordingState.PreviewOnly)
            {
                IsInteracting = !IsInteracting;
            }
        }

        private void InteractionPanel_PointerMoved(object sender, PointerRoutedEventArgs e)
        {
            if (RecordingState > RecordingState.PreviewOnly)
            {
                if (e.Pointer.PointerDeviceType == PointerDeviceType.Mouse) // touch will fire this and compete with tapped event.
                {
                    IsInteracting = true;
                }
            }
        }

        protected override void OnKeyDown(KeyRoutedEventArgs e)
        {
            if (RecordingState > RecordingState.PreviewOnly)
            {
                if (IsInteracting)
                {
                    if (e.Key == VirtualKey.Space)
                    {
                        IsInteracting = false;
                    }
                }
                else
                {
                    IsInteracting = true;
                }
            }
            base.OnKeyDown(e);
        }

        private void CommandBar_Closed(object sender, object e)
        {
            if (RecordingState > RecordingState.PreviewOnly)
            {
                IsInteracting = true;
            }
        }

        private void ProfilesFlyout_Opened(object sender, object e)
        {
            Profiles.AudioCaptureEncodingProfile = captureSession.CaptureEncodingProfile.Audio;
            Profiles.VideoCaptureEncodingProfile = captureSession.CaptureEncodingProfile.Video;
            Profiles.AudioOutputEncodingProfiles = captureSession.AudioOutputEncodingProperties;
            Profiles.VideoOutputEncodingProfiles = captureSession.VideoOutputEncodingProperties;
        }

        private void ProfilesFlyout_Closed(object sender, object e)
        {
            Profiles.AudioCaptureEncodingProfile = null;
            Profiles.VideoCaptureEncodingProfile = null;
            Profiles.AudioOutputEncodingProfiles = null;
            Profiles.VideoOutputEncodingProfiles = null;
        }

        private void DialogOKClicked(object sender, RoutedEventArgs e)
        {
            Preferences.Current.IngestUrl = endpointPrompt.Url;
            EndpointDialog.IsOpen = false;
        }

        private void DialogCancelClicked(object sender, RoutedEventArgs e)
        {
            EndpointDialog.IsOpen = false;
        }

        private async void SystemMediaControls_PropertyChanged(SystemMediaTransportControls sender, SystemMediaTransportControlsPropertyChangedEventArgs e)
        {
            switch (e.Property)
            {
                case SystemMediaTransportControlsProperty.SoundLevel:
                    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
                    {
                        if (sender.SoundLevel != Windows.Media.SoundLevel.Muted)
                        {
#if LOG
                            App.Logger.Log("Unmuted");
#endif
                            await OnResume();
                        }
                        else
                        {
#if LOG
                            App.Logger.Log("Muted");
#endif
                            await OnSuspend();
                        }
                    });
                    break;
            }
        }

        async void App_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e)
        {
#if LOG
            App.Logger.Log("Suspending");
#endif
            Application.Current.Resuming += App_Resuming;
            var deferral = e.SuspendingOperation.GetDeferral();
            await OnSuspend();
            deferral.Complete();
        }

        private async Task OnSuspend()
        {
            recordingTerminated = recordingTerminated || recordingState == RecordingState.Recording;
            await TeardownCamera();
        }

        async void App_Resuming(object sender, object e)
        {
#if LOG
            App.Logger.Log("Resuming");
#endif
            Application.Current.Resuming -= App_Resuming;
            StartMonitoringAppState();
            await OnResume();
        }

        private async Task OnResume()
        {
            await InitializeCamera();
            if (recordingTerminated)
            {
                recordingTerminated = false;
                await RestoreRecording();
            }
        }

        private async Task RestoreRecording()
        {
            MessageDialog msgbox = new MessageDialog("The current broadcast was terminated when the app was suspended. Would you like to restart?");
            var YesCommand = new UICommand("Yes, restart");
            var NoCommand = new UICommand("No");
            msgbox.Commands.Add(YesCommand);
            msgbox.Commands.Add(NoCommand);
            var result = await msgbox.ShowAsync();
            if (result == YesCommand)
            {
                await StartRecordAsync();
            }
        }

        private async void ButtonCamera_Checked(object sender, RoutedEventArgs e)
        {
            var videoDevices = await DeviceInformation.FindAllAsync(Windows.Devices.Enumeration.DeviceClass.VideoCapture);
            var frontCamera = videoDevices.FirstOrDefault(d => GetCameraLocation(d) == Windows.Devices.Enumeration.Panel.Front);

            if (frontCamera != null)
            {
                Preferences.Current.VideoDeviceId = frontCamera.Id;
            }
        }

        private async void ButtonCamera_Unchecked(object sender, RoutedEventArgs e)
        {
            var videoDevices = await DeviceInformation.FindAllAsync(Windows.Devices.Enumeration.DeviceClass.VideoCapture);
            var backCamera = videoDevices.FirstOrDefault(d => GetCameraLocation(d) == Windows.Devices.Enumeration.Panel.Back);

            if (backCamera != null)
            {
                Preferences.Current.VideoDeviceId = backCamera.Id;
            }
        }

        private void ButtonSettings_Click(object sender, RoutedEventArgs e)
        {
            PreferencesFlyout flyout = new PreferencesFlyout();
            flyout.ShowIndependent();
        }

        /// <summary>
        /// Populates the page with content passed during navigation. Any saved state is also
        /// provided when recreating a page from a prior session.
        /// </summary>
        /// <param name="sender">
        /// The source of the event; typically <see cref="NavigationHelper"/>
        /// </param>
        /// <param name="e">Event data that provides both the navigation parameter passed to
        /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested and
        /// a dictionary of state preserved by this page during an earlier
        /// session. The state will be null the first time a page is visited.</param>
        private async void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
        {
            StartMonitoringAppState();
#if LOG
            App.Logger.Log("LoadState");
#endif
            await InitAsync();

            if (Preferences.Current.UsageCount == 0)
            {
                endpointPrompt.Url = Preferences.Current.IngestUrl;
                EndpointDialog.IsOpen = true;
            }
            Preferences.Current.IncrementUsageCount();

            if (e.PageState != null && e.PageState.ContainsKey(Key_RecordingTerminated) && (bool)(e.PageState[Key_RecordingTerminated]))
            {
                e.PageState.Remove(Key_RecordingTerminated);
                await RestoreRecording();
            }
        }

        /// <summary>
        /// Preserves state associated with this page in case the application is suspended or the
        /// page is discarded from the navigation cache.  Values must conform to the serialization
        /// requirements of <see cref="SuspensionManager.SessionState"/>.
        /// </summary>
        /// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param>
        /// <param name="e">Event data that provides an empty dictionary to be populated with
        /// serializable state.</param>
        private void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
        {
            StopMonitoringAppState();
#if LOG
            App.Logger.Log("SaveState");
#endif
            e.PageState.Add(Key_RecordingTerminated, recordingState == RecordingState.Recording);
        }

        private void StartMonitoringAppState()
        {
            SystemMediaTransportControls systemMediaControls = SystemMediaTransportControls.GetForCurrentView();
            systemMediaControls.PropertyChanged += SystemMediaControls_PropertyChanged;
            Window.Current.VisibilityChanged += Window_VisibilityChanged;
            Application.Current.Suspending += App_Suspending;
        }

        private void StopMonitoringAppState()
        {
            Application.Current.Suspending -= App_Suspending;
            Window.Current.VisibilityChanged -= Window_VisibilityChanged;
            SystemMediaTransportControls systemMediaControls = SystemMediaTransportControls.GetForCurrentView();
            systemMediaControls.PropertyChanged -= SystemMediaControls_PropertyChanged;
        }

        #region NavigationHelper registration

        /// The methods provided in this section are simply used to allow
        /// NavigationHelper to respond to the page's navigation methods.
        /// 
        /// Page specific logic should be placed in event handlers for the  
        /// <see cref="GridCS.Common.NavigationHelper.LoadState"/>
        /// and <see cref="GridCS.Common.NavigationHelper.SaveState"/>.
        /// The navigation parameter is available in the LoadState method 
        /// in addition to page state preserved during an earlier session.

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
#if LOG
            App.Logger.Log("OnNavigatedTo: " + e.NavigationMode.ToString());
#endif
            navigationHelper.OnNavigatedTo(e);
        }

        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
#if LOG
            App.Logger.Log("OnNavigatedFrom: " + e.NavigationMode.ToString());
#endif
            navigationHelper.OnNavigatedFrom(e);
        }

        #endregion
        
    }

    public enum RecordingState
    {
        NotRecording = 0,
        PreviewOnly = 1,
        StartRecording = 2,
        Recording = 3,
        EndRecording = 4
    }
}
